# from rest_framework.schemas.openapi import AutoSchema
import importlib

import yaml
from drf_spectacular.openapi import AutoSchema
from rest_framework import serializers
from rest_framework.schemas import SchemaGenerator
from drf_spectacular.utils import inline_serializer

class CustomErrorSerializer(serializers.Serializer):
    code_choices = (0,'Failed'),(1,'Success')
    code = serializers.ChoiceField(choices=code_choices)
    message = serializers.CharField()

def get_type_string(type_value):
    if isinstance(type_value, type):
        if type_value == int:
            return 'integer'
        elif type_value == float:
            return 'number'
        elif type_value == bool:
            return 'boolean'
        elif type_value == str:
            return 'string'
        else:
            return 'string'  # Default to 'string' for unknown types
    else:
        return str(type_value)
    
def custom_response_serailizer(serializer):
    
    serializer_class=None
    name = None
    if isinstance(serializer, serializers.ListSerializer):
        serializer_class = serializer.child.__class__
        name = f"{serializer_class.__name__}_custom_list"
    else:
        serializer_class = serializer.__class__
        name = f"{serializer_class.__name__}_custom"
    return {
        200:inline_serializer(name=name,fields={
        'code': serializers.IntegerField(),
        'data': serializer
        }),
        400: CustomErrorSerializer(),
        500: CustomErrorSerializer()
    }

def build_object_type_schema(serializer_class):
    fields = serializer_class().get_fields()
    properties = {}
    required = []
    for field_name, field in fields.items():
        field_schema = build_schema_for_field(field)
        if not field.read_only:
            required.append(field_name)
        properties[field_name] = field_schema
    return {
        'type': 'object',
        'properties': properties,
        'required': required,
    }


def build_serializer_example(serializer_class):
    generator = SchemaGenerator()
    request_data = {}
    for field_name, field in serializer_class().fields.items():
        if not field.read_only:
            field_schema = build_schema_for_field(field)
            field_example = \
                get_random_value_from_schema(generator, field_schema)

            request_data[field_name] = field_example
    return request_data


def build_schema_for_field(field):
    field_schema = {'type': 'string'}  # default schema
    if isinstance(field, serializers.IntegerField):
        field_schema = {'type': 'integer'}
    elif isinstance(field, serializers.FloatField):
        field_schema = {'type': 'number', 'format': 'float'}
    elif isinstance(field, serializers.DecimalField):
        field_schema = {'type': 'number', 'format': 'decimal'}
    elif isinstance(field, serializers.BooleanField):
        field_schema = {'type': 'boolean'}
    elif isinstance(field, serializers.DateField):
        field_schema = {'type': 'string', 'format': 'date'}
    elif isinstance(field, serializers.DateTimeField):
        field_schema = {'type': 'string', 'format': 'date-time'}
    elif isinstance(field, serializers.ChoiceField):
        choices = list(field.choices.keys())
        if all(isinstance(choice, int) for choice in choices):
            field_schema = {'type': 'integer', 'enum': choices}
        else:
            field_schema = {'type': 'string', 'enum': choices}
    elif isinstance(field, serializers.ListField):
        field_schema = {
            'type': 'array',
            'items': build_schema_for_field(field.child)
        }
    elif isinstance(field, serializers.DictField):
        field_schema = {
            'type': 'object',
            'additionalProperties': build_schema_for_field(field.child)
        }
    elif isinstance(field, serializers.Serializer):
        field_schema = build_object_type_schema(field.__class__)
    return field_schema


def get_random_value_from_schema(generator, schema):
    if 'enum' in schema:
        return schema['enum'][0] if schema['enum'] else None
    if schema['type'] == 'string':
        return 'example'
    if schema['type'] == 'integer':
        return 0
    if schema['type'] == 'number':
        return 0.0
    if schema['type'] == 'boolean':
        return True
    if schema['type'] == 'array':
        return [get_random_value_from_schema(generator, schema['items'])]
    if schema['type'] == 'object':
        properties = {}
        for prop_name, prop_schema in schema['properties'].items():
            properties[prop_name] = \
                get_random_value_from_schema(generator, prop_schema)

        return properties
    model_class = generator.get_model_from_relation(schema.get('$ref'))
    if model_class is not None:
        model_meta = model_meta.get_field_info(model_class)
        return build_object_type_example(model_meta)
    return None


def load_class(class_path):
    module_path, class_name = class_path.rsplit(".", 1)
    module = importlib.import_module(module_path)
    return getattr(module, class_name)


# Reference
# https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#externalDocumentationObject
class AutoDocSchema(AutoSchema):
    

    @property
    def documentation(self):
        '''example usage
        desc: function desc

        parameters:
            petId:
                in: path|query|header|cookie
                required: true|false (default true)
                description: optional
                example: optional
                type: integer|string|boolean|number|object|array
                (default string)

        json:
            first_name:
                type: string
                example: John
            age:
                type: number
            petName: string

        response:
            not support yet, use @extend_schema,
            and openapi.Response for better support instead.

            @extend_schema(
                responses={
                    200: openapi.Response(
                        description='Successful response',
                        schema=Serializer(
                            data={
                                'code': openapi.TYPE_INTEGER,
                                'message': openapi.TYPE_STRING,
                                'id': openapi.TYPE_STRING,
                                'parts': PartsSerializer(many=True),
                            },
                            required=['code', 'message', 'id', 'parts']
                        ),
                    ),
                    ...
                }
            )
        '''
        if not hasattr(self, '_documentation'):
            try:
                doc_str = self.get_description()
                if doc_str:
                    self._documentation = yaml.safe_load(doc_str)
                else:
                    self._documentation = {}
            except yaml.scanner.ScannerError as ex:
                print(ex)
                self._documentation = {}
        return self._documentation

    def get_request_data_fields(self):
        # return []
        action_or_method = getattr(self.view, getattr(self.view, 'action', self.method.lower()), None)
        request_data_keys = getattr(action_or_method, 'request_data_keys', [])
        request_data_types = getattr(action_or_method, 'request_data_types', {})
        request_data_optional = getattr(action_or_method, 'request_data_optional', [])
        # Create the request data fields based on the keys and types
        fields = {}
        for key in request_data_keys:
            type_value = request_data_types.get(key, str)  # Get the type value
            fields[key] = {
                'required': key not in request_data_optional,
                'type': get_type_string(type_value)
            }

        return fields
    
    def get_request_data_fields_get(self):
        # return []
        action_or_method = getattr(self.view, getattr(self.view, 'action', self.method.lower()), None)
        request_data_keys = getattr(action_or_method, 'request_data_keys', [])
        request_data_types = getattr(action_or_method, 'request_data_types', {})
        request_data_optional = getattr(action_or_method, 'request_data_optional', [])
        # Create the request data fields based on the keys and types
        fields = []
        for key in request_data_keys:
            type_value = request_data_types.get(key, str)  # Get the type value
            fields.append({
                'name': key,
                'required': key not in request_data_optional,
                'schema':{
                    'type': get_type_string(type_value)
                }
            })

        return fields
        
        
    @property
    def auto_parameters(self):
        params = self.documentation.get('parameters', {})
        ret = []
        inserted = []
        for name, param in params.items():
            if isinstance(param, str):
                param = {'type': param}
            elif not isinstance(param, dict):
                continue
            
            inserted.append(name)

            param.setdefault('name', name)
            param.setdefault('required', True)
            param.setdefault('in', 'query')

            if 'type' in param:
                param['schema'] = {'type': param.pop('type')}
            else:
                param['schema'] = {'type': 'string'}

            ret.append(param)
        if self.method.lower() =='get':
            ext = self.get_request_data_fields_get()
            for e in ext:
                if 'name' not in e:
                    continue
                if  e['name'] not in inserted:
                    ret.append(e)
        return ret

    @property
    def auto_response(self):
        response = self.documentation.get('response', {})
        ret = {}
        if not isinstance(response,dict):
            return None
        for code, re_obj in response.items():
            # init schema with json format
            schema = {
                'description': re_obj.get('desc', ''),
                'content': {
                    'application/json': {
                        'schema': {}
                    }
                }
            }

            schema['content']['application/json']['schema'] = {
                'type': re_obj.get('type', 'object'),
                # 'example': re_obj.get('example', None),
                'properties': {
                    props_key: (props if isinstance(props, dict)
                                else {'type': props})
                    for props_key, props in re_obj.items() if props_key not in ('desc', 'type','example','items')
                }
            }

            ret[code] = schema
        return ret

    @property
    def auto_request_body(self):
        # Retrieve the json from the documentation
        json = self.documentation.get('json', {})
        
        # # If there is no json, return None
        # if not json:
            
        
        # Call the get_request_data_fields method to get the request data fields
        request_data_fields = {}
        if self.method.lower() != 'get':
            request_data_fields = self.get_request_data_fields()
        request_data_fields.update({
                            key: (props if isinstance(props, dict)
                                  else {'type': props})
                            for key, props in json.items()
                        })
        # Construct the request body schema
        request_body = {
            'content': {
                'application/json': {
                    'schema': {
                        'type': 'object',
                        'properties': request_data_fields
                    }
                }
            }
        }
        # print(request_data_fields)
        # Add the request data fields to the request body schema
        # if request_data_fields:
        #     request_body['content']['application/json']['schema']['properties'].update(request_data_fields)
        
        return request_body

    def get_operation(self, path, path_regex, path_prefix, method, registry):
        operation = \
            super().get_operation(
                path, path_regex, path_prefix, method, registry
            )

        if 'parameters' not in operation:
            operation['parameters'] = []

        operation['description'] = \
            self.documentation.get('desc', 'No description available.')

        operation['parameters'].extend(self.auto_parameters)
        if self.method.lower() != 'get':
            operation['requestBody'] = self.auto_request_body or None
            # operation['responses'] = self.auto_response or None
        response = self.auto_response or None
        if response:
            operation['responses'] = self.auto_response

        return operation
